package com.uduemc.biso.node.web.scheduled;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageInfo;
import com.uduemc.biso.core.extities.center.Host;
import com.uduemc.biso.core.extities.center.Site;
import com.uduemc.biso.node.core.entities.HBackup;
import com.uduemc.biso.node.core.entities.HBackupSetup;
import com.uduemc.biso.node.core.node.config.OperateLoggerStaticModel;
import com.uduemc.biso.node.core.node.config.OperateLoggerStaticModelAction;
import com.uduemc.biso.node.core.operate.entities.OperateLog;
import com.uduemc.biso.node.core.property.GlobalProperties;
import com.uduemc.biso.node.core.utils.SitePathUtil;
import com.uduemc.biso.node.web.api.config.SpringContextUtil;
import com.uduemc.biso.node.web.api.service.*;
import com.uduemc.biso.node.web.api.service.impl.BackupRestoreServiceImpl;
import com.uduemc.biso.node.web.api.service.impl.HostBackupRestoreServiceImpl;
import com.uduemc.biso.node.web.service.OperateLoggerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Component
@Slf4j
public class BackupScheduled {

    @Resource
    private HBackupSetupService hBackupSetupServiceImpl;

    @Resource
    private HBackupService hBackupServiceImpl;

    @Resource
    private HostService hostServiceImpl;

    @Resource
    private SiteService siteServiceImpl;

    @Resource
    private HostSetupService hostSetupServiceImpl;

    @Resource
    private BackupRestoreService backupRestoreServiceImpl;

    @Resource
    private OperateLoggerService operateLoggerServiceImpl;

    @Resource
    private GlobalProperties globalProperties;

    @Resource
    private SpringContextUtil springContextUtil;

    /**
     * 每5秒钟
     */
//    @Scheduled(cron = "*/5 * * * * ?")

    /**
     * 每天0点执行一次对备份文件的校验。找出备份数据存在，而备份文件不存在的数据，修改数据状态;找出备份文件存在而备份数据不存在的备份文件删除
     */
    @Async
    @Scheduled(cron = "0 0 0 * * ?")
    void backupDataCheck() {
        // 获取所有备份数据进行校验，备份文件不存在的更改当前数据正常的状态为异常，同时写入原因。
        validBackupData();

        // 清空用户站点目录下的临时目录
        cleanHostTmpDir();
    }

    void validBackupData() {
        boolean local = springContextUtil.local();
        if (local) {
            return;
        }

        int page = 1;
        int size = 12;
        String basePath = globalProperties.getSite().getBasePath();
        PageInfo<HBackup> pageInfo = null;

        do {
            try {
                pageInfo = hBackupServiceImpl.findOKPageInfoAll(-1, "", (short) -1, page, size);
            } catch (IOException e) {
            }
            if (pageInfo == null) {
                break;
            }

            List<HBackup> list = pageInfo.getList();
            if (CollUtil.isEmpty(list)) {
                break;
            }

            List<Host> listHost = new ArrayList<>();
            for (HBackup hBackup : list) {
                Long hBackupId = hBackup.getId();
                Long hostId = hBackup.getHostId();
                if (hostId == null) {
                    continue;
                }

                Host host = null;
                if (CollUtil.isNotEmpty(listHost)) {
                    Optional<Host> findFirst = listHost.stream().filter(item -> item.getId().longValue() == hostId.longValue()).findFirst();
                    if (findFirst.isPresent()) {
                        host = findFirst.get();
                    }
                }
                try {
                    host = hostServiceImpl.getInfoById(hostId);
                } catch (IOException e) {
                }

                if (host == null) {
                    // 写入日志
                    log.error("通过 hostId 未能获取到 host 数据。hostId：" + hostId);
                    continue;
                }
                String backupDataPath = null;
                try {
                    backupDataPath = BackupRestoreServiceImpl.makeBackupDirectory(host, basePath, hBackup);
                } catch (IOException e) {
                }
                if (StrUtil.isBlank(backupDataPath)) {
                    log.error("通过 host, basePath, hBackupId 未能获取到 backupDataPath 数据。host：" + host + ", basePath：" + basePath + ", hBackupId：" + hBackupId);
                    continue;
                }

                if (!FileUtil.isFile(backupDataPath)) {
                    // 文件不存在，改变备份数据的状态
                    String backupErrmsg = hBackup.getBackupErrmsg();
                    if (StrUtil.isBlank(backupErrmsg)) {
                        backupErrmsg = "系统自查校验，备份数据文件不存在！";
                    } else {
                        backupErrmsg += "|系统自查校验，备份数据文件不存在！";
                    }
                    hBackup.setStatus((short) 3).setBackupErrmsg(backupErrmsg);
                    try {
                        hBackupServiceImpl.updateByPrimaryKey(hBackup);
                    } catch (IOException e) {
                    }
                }
            }

        } while (pageInfo.isHasNextPage());
    }

    void cleanHostTmpDir() {
        boolean local = springContextUtil.local();
        if (local) {
            return;
        }
        int page = 1;
        int size = 200;
        String basePath = globalProperties.getSite().getBasePath();
        PageInfo<Host> pageInfo = null;
        do {
            try {
                pageInfo = hostServiceImpl.findByWhere(page++, size);
            } catch (IOException e) {
            }
            if (pageInfo == null) {
                break;
            }

            List<Host> list = pageInfo.getList();
            if (CollUtil.isEmpty(list)) {
                break;
            }

            for (Host host : list) {
                String tmpPath = SitePathUtil.getUserTMPPathByCode(basePath, host.getRandomCode());
                if (FileUtil.isDirectory(tmpPath)) {
                    File file = new File(tmpPath);
                    if (FileUtil.size(file) > 0) {
                        FileUtil.del(file);
                        FileUtil.mkdir(file);
                    }
                }
            }

        } while (pageInfo.isHasNextPage());

    }


//	/**
//	 * 每5秒钟
//	 */
//	@Scheduled(cron = "*/5 * * * * ?")

    /**
     * 每天凌晨0点30分执行
     */
    @Async
    @Scheduled(cron = "0 30 0 * * ?")
    void autoBackup() {
        log.info("自动备份-->定时任务开始。");
        // 获取需要做定时备份数据
        List<HBackupSetup> listHBackupSetup = null;
        try {
            listHBackupSetup = hBackupSetupServiceImpl.findAllByNoType0AndStartAtNow();
        } catch (IOException e) {
        }

        if (CollUtil.isEmpty(listHBackupSetup)) {
            log.info("自动备份-->没有需要备份的站点，自动备份定时任务结束。");
            return;
        }

        // 过滤掉非今天需要备份的站点数据
        List<HBackupSetup> list = filterBackupToday(listHBackupSetup);
        if (CollUtil.isNotEmpty(list)) {
            // 正常情况下的数据备份
            log.info("自动备份-->正常备份共执行" + list.size() + "个站点数据。");
            for (HBackupSetup hBackupSetup : list) {
                normalBackup(hBackupSetup);
                // 保留配置中一定的备份数量，按照时间顺序删除最早的、多余的备份数据。
                try {
                    hBackupServiceImpl.deleteExtraAutoBackupData(hBackupSetup);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // 之前备份异常情况下的数据备份
        List<HBackupSetup> otherList = filterSupplementBackup(listHBackupSetup, list);
        if (CollUtil.isNotEmpty(otherList)) {
            // 异常情况下的数据备份
            log.info("自动备份-->异常补充备份共执行" + otherList.size() + "个站点数据。");
            for (HBackupSetup hBackupSetup : otherList) {
                supplementBackup(hBackupSetup);
                // 保留配置中一定的备份数量，按照时间顺序删除最早的、多余的备份数据。
                try {
                    hBackupServiceImpl.deleteExtraAutoBackupData(hBackupSetup);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        log.info("自动备份-->定时任务结束。");
    }

    // 补充备份
    void supplementBackup(HBackupSetup hBackupSetup) {
        String filename = DateUtil.format(DateUtil.date(), "MMdd") + "补充备份";
        backup(hBackupSetup, filename, true);
    }

    // 正常备份
    void normalBackup(HBackupSetup hBackupSetup) {
        String filename = DateUtil.format(DateUtil.date(), "MMdd") + "自动备份";
        backup(hBackupSetup, filename, false);
    }

    // 备份, supplement：是否补充备份 true 是， false 否。
    void backup(HBackupSetup hBackupSetup, String filename, boolean supplement) {
        String systemLogType = supplement ? "补充备份" : "正常备份";
        Short type = hBackupSetup.getType();
        type = type == null ? 0 : type.shortValue();
        if (type == 0) {
            return;
        }
        Long hostId = hBackupSetup.getHostId();
        Host host = null;
        try {
            host = hostServiceImpl.getInfoById(hostId);
        } catch (IOException e) {
            log.error("自动备份-->" + systemLogType + "-->未获取到host数据，IOException e:" + e.getMessage());
        }
        if (host == null) {
            log.error("自动备份-->" + systemLogType + "-->未获取到host数据，hBackupSetup:" + hBackupSetup);
            return;
        }
        log.info("自动备份-->" + systemLogType + "-->开始备份 host:" + host);

        // 写入操作日志
        OperateLog backupLog = null;
        try {
            backupLog = backupLog(host, filename);
        } catch (IOException e) {
            log.error("自动备份-->" + systemLogType + "-->未能成功写入自动备份系统日志，IOException e:" + e.getMessage());
        }
        if (backupLog == null) {
            log.error("自动备份-->" + systemLogType + "-->未能成功写入自动备份系统日志，host:" + host);
            return;
        }

        String description = "";
        if (type == 1) {
            description = (supplement ? "系统补充备份，" : "") + "触发月备份规则自动备份";
        } else if (type == 2) {
            description = (supplement ? "系统补充备份，" : "") + "触发周备份规则自动备份";
        } else if (type == 3) {
            description = (supplement ? "系统补充备份，" : "") + "触发自定义备份规则自动备份";
        }
        // 写入备份数据
        HBackup hBackup = null;
        try {
            hBackup = insertHBackup(host, filename, description);
        } catch (IOException e) {
            log.error("自动备份-->" + systemLogType + "-->未能成功写入备份数据，IOException e:" + e.getMessage());
        }
        if (hBackup == null) {
            log.error("自动备份-->" + systemLogType + "-->未能成功写入备份数据，host:" + host);
            return;
        }

        long space = hostSetupServiceImpl.getSpace(hostId);
        long usedSpace = hostSetupServiceImpl.getUsedSpace(host);
        if (usedSpace > space) {
            backupLog.setStatus((short) 3);
            hBackup.setStatus((short) 3).setSize(0L).setFilepath("").setBackupErrmsg("空间不足，请联系管理员！");
            try {
                operateLoggerServiceImpl.update(backupLog);
                hBackupServiceImpl.updateByPrimaryKey(hBackup);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }

        File file = backupRestoreServiceImpl.backupRun(host, hBackup);

        if (file != null && file.isFile()) {
            backupLog.setStatus((short) 1);
            hBackup.setStatus((short) 1).setSize(FileUtil.size(file));
            String basePath = globalProperties.getSite().getBasePath();
            hBackup.setFilepath(FileUtil.subPath(SitePathUtil.getUserPathByCode(basePath, host.getRandomCode()), file));
            log.error("自动备份-->" + systemLogType + "-->备份成功，host:" + host);
        } else {
            backupLog.setStatus((short) 3);
            hBackup.setStatus((short) 3).setSize(0L).setFilepath("").setBackupErrmsg("备份失败，请联系管理员！");
            log.error("自动备份-->" + systemLogType + "-->备份失败，host:" + host);
        }
        try {
            operateLoggerServiceImpl.update(backupLog);
            hBackupServiceImpl.updateByPrimaryKey(hBackup);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    // 写入备份数据
    HBackup insertHBackup(Host host, String filename, String description) throws IOException {
        Long hostId = host.getId();
        // 首先写入备份的数据
        HBackup hBackup = new HBackup();
        hBackup.setHostId(hostId).setType((short) 1).setFilename(filename).setFilepath("").setSize(0L).setDescription(description).setStatus((short) 2)
                .setBackupErrmsg("");
        return hBackupServiceImpl.insert(hBackup);
    }

    // 写入备份日志
    OperateLog backupLog(Host host, String filename) throws IOException {
        Long hostId = host.getId();
        String declaringTypeName = "com.uduemc.biso.node.web.api.controller.HostController";
        String modelName = OperateLoggerStaticModel.findModelName(declaringTypeName);
        String modelAction = OperateLoggerStaticModel.findModelAction(declaringTypeName, OperateLoggerStaticModelAction.HOST_BACKUP);

        List<Site> sites = siteServiceImpl.getSiteList(hostId);
        String content = "自动备份，对整个网站的所有数据进行备份操作。备份名：" + filename;
        if (CollUtil.size(sites) > 1) {
            content = "自动备份，对整个网站的所有语言版本的数据进行备份操作。备份名：" + filename;
        }

        short status = 2;

        Site defaultSite = siteServiceImpl.getDefaultSite(hostId);
        Long siteId = defaultSite.getId();
        Integer languageId = defaultSite.getLanguageId();

        Long loginUserId = 0L;
        String loginUserType = "systemBackupUser";
        String loginUserName = "系统";
        String ipAddress = "系统服务器";

        OperateLog operateLog = new OperateLog();
        operateLog.setHostId(hostId).setSiteId(siteId).setLanguageId(languageId).setUserId(loginUserId).setUserType(loginUserType).setUsername(loginUserName)
                .setModelName(modelName).setModelAction(modelAction).setContent(content).setParam("").setReturnValue("").setOperateItem("").setStatus(status)
                .setIpaddress(ipAddress);

        return operateLoggerServiceImpl.insert(operateLog);
    }

    List<HBackupSetup> filterSupplementBackup(List<HBackupSetup> all, List<HBackupSetup> backup) {
        List<HBackupSetup> result = new ArrayList<>();
        for (HBackupSetup hBackupSetup : all) {
            // 首先不能在 backup 的数据中
            if (CollUtil.isNotEmpty(backup)) {
                Optional<HBackupSetup> findFirst = backup.stream().filter(item -> item.getId().longValue() == hBackupSetup.getId().longValue()).findFirst();
                if (findFirst.isPresent()) {
                    continue;
                }
            }
            // 今天是否有执行过自动备份，如果有则不再执行
            List<HBackup> byToday = null;
            try {
                byToday = hBackupServiceImpl.filterByDay(hBackupSetup.getHostId(), (short) 1, DateUtil.date().toLocalDateTime().toLocalDate());
            } catch (IOException e) {
            }
            if (CollUtil.isNotEmpty(byToday)) {
                continue;
            }

            Short type = hBackupSetup.getType();
            type = type == null ? (short) 0 : type.shortValue();
            if (type == 1) {
                // 通过时间范围获取最后一次自动备份的数据，如果能获取到则不需要补充备份，如果获取不到则需要补充备份
                Integer monthly = hBackupSetup.getMonthly();
                monthly = monthly == null ? 0 : monthly.intValue();
                if (monthly < 1) {
                    continue;
                }

                // 上一次备份的日期
                Date startAt = hBackupSetup.getStartAt();
                LocalDate lastMonthBackupDate = HostBackupRestoreServiceImpl.lastMonthBackupDate(startAt, monthly);
                if (lastMonthBackupDate != null) {
                    List<HBackup> filterByDay = null;
                    try {
                        filterByDay = hBackupServiceImpl.filterByDay(hBackupSetup.getHostId(), (short) 1, lastMonthBackupDate);
                    } catch (IOException e) {
                    }
                    if (CollUtil.isEmpty(filterByDay)) {
                        result.add(hBackupSetup);
                    }
                }
            } else if (type == 2) {
                // 如果理论上，上一次备份的日期小于开始时间，则不进行补充备份规则
                Integer weekly = hBackupSetup.getWeekly();
                weekly = weekly == null ? 0 : weekly.intValue();
                if (weekly < 1) {
                    continue;
                }
                // 上一次备份的日期
                Date startAt = hBackupSetup.getStartAt();
                LocalDate lastWeekBackupDate = HostBackupRestoreServiceImpl.lastWeekBackupDate(startAt, weekly);
                if (lastWeekBackupDate != null) {
                    List<HBackup> filterByDay = null;
                    try {
                        filterByDay = hBackupServiceImpl.filterByDay(hBackupSetup.getHostId(), (short) 1, lastWeekBackupDate);
                    } catch (IOException e) {
                    }
                    if (CollUtil.isEmpty(filterByDay)) {
                        result.add(hBackupSetup);
                    }
                }
            } else if (type == 3) {
                // 自定义
                Integer daily = hBackupSetup.getDaily();
                if (daily == null || daily.intValue() < 1) {
                    continue;
                }
                if (daily.intValue() == 1) {
                    result.add(hBackupSetup);
                    continue;
                }
                // 上一次备份的日期
                Date startAt = hBackupSetup.getStartAt();
                LocalDate lastCustomBackupDate = HostBackupRestoreServiceImpl.lastCustomBackupDate(startAt, daily);
                if (lastCustomBackupDate != null) {
                    List<HBackup> filterByDay = null;
                    try {
                        filterByDay = hBackupServiceImpl.filterByDay(hBackupSetup.getHostId(), (short) 1, lastCustomBackupDate);
                    } catch (IOException e) {
                    }
                    if (CollUtil.isEmpty(filterByDay)) {
                        result.add(hBackupSetup);
                    }
                }
            }
        }
        return result;
    }

    List<HBackupSetup> filterBackupToday(List<HBackupSetup> listHBackupSetup) {
        List<HBackupSetup> result = new ArrayList<>();
        for (HBackupSetup hBackupSetup : listHBackupSetup) {
            // 今天是否有执行过自动备份，如果有则不再执行
            List<HBackup> byToday = null;
            try {
                byToday = hBackupServiceImpl.filterByDay(hBackupSetup.getHostId(), (short) 1, DateUtil.date().toLocalDateTime().toLocalDate());
            } catch (IOException e) {
            }
            if (CollUtil.isNotEmpty(byToday)) {
                continue;
            }

            Short type = hBackupSetup.getType();
            type = type == null ? (short) 0 : type.shortValue();
            if (type == 1) {
                // 月周期
                Integer monthly = hBackupSetup.getMonthly();
                if (monthly == null) {
                    continue;
                }
                if (monthly.intValue() == DateUtil.thisDayOfMonth()) {
                    result.add(hBackupSetup);
                }
            } else if (type == 2) {
                // 周周期
                Integer weekly = hBackupSetup.getWeekly();
                if (weekly == null) {
                    continue;
                }
                LocalDate now = LocalDate.now();
                DayOfWeek dayOfWeek = now.getDayOfWeek();
                if (weekly.intValue() == dayOfWeek.getValue()) {
                    result.add(hBackupSetup);
                }
            } else if (type == 3) {
                // 自定义
                Integer daily = hBackupSetup.getDaily();
                if (daily == null || daily.intValue() < 1) {
                    continue;
                }
                if (daily.intValue() == 1) {
                    result.add(hBackupSetup);
                    continue;
                }
                // 根据开始时间距离今天的天数进行取余后计算今天是否需要备份
                Date startAt = hBackupSetup.getStartAt();
                LocalDate todayLocalDate = DateUtil.date().toLocalDateTime().toLocalDate();
                LocalDate nextDate = HostBackupRestoreServiceImpl.nextCustomBackupDate(startAt, daily);
                long betweenDay = DateUtil.betweenDay(DateUtil.date(nextDate), DateUtil.date(todayLocalDate), true);
                if (betweenDay == 0) {
                    result.add(hBackupSetup);
                    continue;
                }
            }
        }
        return result;
    }

}
